export interface Position {
  x: number;
  y: number;
}

export interface Style {
  fillStyle?: string;
  strokeStyle?: string;
  lineWidth?: number;
  font?: string;
  fontSize?: string;
  textAlign?: "start" | "end" | "left" | "right" | "center";
  textBaseline?:
    | "top"
    | "hanging"
    | "middle"
    | "alphabetic"
    | "ideographic"
    | "bottom";
}

const PIXEL_RATIO = 2;

/**
 * HTML Canvas API 封装
 */
export class Canvas {
  private element: HTMLCanvasElement;
  private context: CanvasRenderingContext2D;

  constructor(
    readonly container: HTMLElement,
    readonly width: number,
    readonly height: number
  ) {
    this.element = document.createElement("canvas");
    this.element.width = width * 2;
    this.element.height = height * 2;
    this.element.style.width = `${width}px`;
    this.element.style.height = `${height}px`;
    container.appendChild(this.element);
    this.context = this.element.getContext("2d");
  }

  private convertPoint(position: Position): Position {
    return {
      x: position.x * PIXEL_RATIO,
      y: (this.height - position.y) * PIXEL_RATIO
    };
  }

  private convertPixel(n: number): number {
    return n * PIXEL_RATIO;
  }

  private applyStyle(style?: Style) {
    this.context.save();
    if (style) {
      this.context.fillStyle = style.fillStyle;
      this.context.strokeStyle = style.strokeStyle;
      this.context.lineWidth = style.lineWidth;
      if (style.font) {
        this.context.font = style.font;
      }
      if (style.textAlign) {
        this.context.textAlign = style.textAlign;
      }
      if (style.textBaseline) {
        this.context.textBaseline = style.textBaseline;
      }
    }
  }

  private resetStyle() {
    this.context.restore();
  }

  /**
   * 清除画布
   * @param rect
   */
  public clear(rect?: [Position, Position]): void {
    let x = 0;
    let y = 0;
    let w = this.element.width;
    let h = this.element.height;

    if (rect) {
      x = Math.min(rect[0].x, rect[1].x);
      y = Math.max(rect[0].y, rect[1].y);
    }

    this.context.clearRect(x, y, w, h);
  }

  /**
   * 绘制文字
   * @param text
   * @param point
   * @param style
   */
  public drawText(text: string, point: Position, style?: Style): void {
    const p = this.convertPoint(point);

    this.applyStyle(style);

    this.context.fillText(text, p.x, p.y);

    this.resetStyle();
  }

  /**
   * 绘制圆点
   * @param point
   * @param radius
   * @param style
   */
  public drawPoint(point: Position, radius: number, style?: Style) {
    const { x, y } = this.convertPoint(point);
    const r = this.convertPixel(radius);

    this.applyStyle(style);

    this.context.beginPath();
    this.context.arc(x, y, r, 0, Math.PI * 2);
    if (style?.fillStyle) {
      this.context.fill();
    }
    if (style?.strokeStyle) {
      this.context.stroke();
    }

    this.resetStyle();
  }

  /**
   * 绘制折线
   * @param point1
   * @param point2
   * @param style
   */
  public drawLine(point1: Position, point2: Position, style?: Style): void {
    const p1 = this.convertPoint(point1);
    const p2 = this.convertPoint(point2);

    this.applyStyle(style);

    this.context.beginPath();
    this.context.moveTo(p1.x, p1.y);
    this.context.lineTo(p2.x, p2.y);
    this.context.stroke();

    this.resetStyle();
  }

  /**
   * 绘制矩形
   * @param point1
   * @param point2
   * @param style
   */
  public drawRect(point1: Position, point2: Position, style?: Style): void {
    const p1 = this.convertPoint(point1);
    const p2 = this.convertPoint(point2);
    const x = Math.min(p1.x, p2.x);
    const y = Math.min(p1.y, p2.y);
    const w = Math.abs(p1.x - p2.x);
    const h = Math.abs(p1.y - p2.y);

    this.applyStyle(style);

    this.context.beginPath();
    this.context.rect(x, y, w, h);
    if (style?.fillStyle) {
      this.context.fill();
    }
    if (style?.strokeStyle) {
      this.context.stroke();
    }

    this.resetStyle();
  }

  /**
   * 绘制弧形
   * @param center
   * @param radius
   * @param startAngle
   * @param endAngle
   * @param style
   */
  public drawArc(
    center: Position,
    radius: number,
    startAngle: number,
    endAngle: number,
    style?: Style
  ): void {
    const p = this.convertPoint(center);
    const r = this.convertPixel(radius);

    this.applyStyle(style);

    this.context.beginPath();
    if (style?.fillStyle) {
      this.context.moveTo(p.x, p.y);
    }
    this.context.arc(p.x, p.y, r, startAngle, endAngle);
    if (style?.fillStyle) {
      this.context.fill();
    }
    if (style?.strokeStyle) {
      this.context.stroke();
    }

    this.resetStyle();
  }
}
